package instructions;

import values.*;
import values.Boolean;
import values.Integer;
import base.AST;
import base.Environment;
import base.Identifier;

public class Case implements Instruction {

	AST name;
	AST pattern;
	Instruction instrFail, instrMatch;
	
	/**
	 *  Creates the instruction 
	 *  case name
	 *  	of pattern {instrMatch}
	 *  	else {instrFail}
	 */
	public Case(AST name, AST pattern, Instruction instrMatch, Instruction instrFail) {
		assert ((pattern instanceof Identifier || pattern instanceof Value) && !(pattern instanceof Function));
		assert ((name instanceof Identifier || name instanceof Value) && !(name instanceof Function));

		this.name = name;
		this.pattern = pattern;
		this.instrMatch = instrMatch;
		this.instrFail = instrFail;
	}

	/**
	 * if the name matches the pattern, executes instrMatch, else executes instrFail
	 */
	public Environment instr(Environment env) throws BadInstructionException {
		Environment newEnv = env.clone();
		boolean comparison = compareTo(this.pattern, this.name, newEnv);

		if (comparison) {
			newEnv = instrMatch.instr(newEnv);
		} else {
			newEnv = instrFail.instr(newEnv);
		}
		return newEnv;
	}

	private boolean compareTo(AST pattern, AST name, Environment env) {
		Value tempName, tempPattern;
		Environment tempEnv = env.clone();

		// is the pattern is an identifier : always true
		if (pattern instanceof Identifier) {
			//If the identifier used by the pattern is already in the environment, compare the value of the pattern with the value  
			if (env.contains((Identifier)pattern)) {
				tempPattern = env.getValue((Identifier) pattern);
			} else {
				// add the value of pattern to match the one of name in the environment
				env.add((Identifier)pattern);
				env.update((Identifier)pattern, evalAST(name, tempEnv));
				//env = tempEnv; // FIXME might not be corect in java
				return true; // if the pattern is an identifier, any value can be match to it
			}
			// if the pattern is not an identifier and
		} else {

			tempPattern = (Value) pattern;
		}
		// if the name is an identifier
		if (name instanceof Identifier) {
			assert (env.contains(((Identifier) name))); // The identifier HAS to be in the environment
			tempName = env.getValue((Identifier)name);
		} else {
			tempName = (Value) name;
		}
		// two easy cases:
		// Boolean
		if (tempPattern instanceof Boolean) {
			// if the two booleans are of the same type
			if ( ((Boolean)(tempName)).eval() == ((Boolean)tempPattern).eval()) {
				return true;
			} else {
				return false;
			}
		}
		// Integer
		if (tempPattern instanceof Integer) {
			if ( ((Integer)(tempName)).eval() == ((Integer)tempPattern).eval()) {
				return true;
			} else {
				return false;
			}
		}
		// two hard cases:
		// Array
		if (tempPattern instanceof Array) {
			// if the name is not an array: can't compare an array to another value
			if (! (tempName instanceof Array)) {
				return false;
			} else {
				// if both are arrays, but of different sizes
				if (((Array) tempPattern).getSize() != ((Array)tempName).getSize()) {
					return false;
				} else {
					for (int i = 0; i < ((Array)tempPattern ).getSize(); i++) {
						if (!compareTo(((Array) tempPattern ).eval(i),((Array) tempName).eval(i), env)) {
							return false; // if at least one of the values of the array is not comparable
						}
					}
					// if all values matched the pattern values
					return true;
				}
			}

		}
		// PatternArray
		if (tempPattern instanceof PatternArray) {
			// if the name is not an array: can't compare an array to another value
			if (! (tempName instanceof Array)) {
				return false;
			} else {
				// if both are arrays, but of different sizes
				if (((PatternArray) tempPattern).getSize() != ((Array)tempName).getSize()) {
					return false;
				} else {
					for (int i = 0; i < ((PatternArray)tempPattern).getSize(); i++) {
						if (!compareTo(((PatternArray) tempPattern).eval(i),((Array) tempName).eval(i), env)) {
							return false; // if at least one of the values of the array is not comparable
						}
					}
					// if all values matched the pattern values
					return true;
				}
			}

		}
		return false; // should never come to this
	}


	private Value evalAST(AST name, Environment env) {
		if (name instanceof Identifier) {
			return env.getValue((Identifier) name);
		} else {
			if (name instanceof Boolean) {
				return ((Boolean) name);
			}
			if (name instanceof Integer) {
				return ((Integer) name);
			}
			if (name instanceof Array) {
				return ((Array) name);
			}
		}
		return null; // should never happen
	}
}

